{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# NK models of evolution\n",
    "\n",
    "Code examples from [Think Complexity, 2nd edition](http://greenteapress.com/wp/complexity2), Chapter 11\n",
    "\n",
    "Copyright 2016 Allen Downey, [MIT License](http://opensource.org/licenses/MIT)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import print_function, division\n",
    "\n",
    "%matplotlib inline\n",
    "%precision 3\n",
    "\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "\n",
    "import numpy as np\n",
    "from numpy.random import rand, randint, permutation\n",
    "\n",
    "from thinkstats2 import Cdf\n",
    "from thinkstats2 import RandomSeed\n",
    "\n",
    "import thinkplot\n",
    "\n",
    "from matplotlib import rc\n",
    "rc('animation', html='html5')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The NK landscape\n",
    "\n",
    "Here's an implementation of an NK landscape.\n",
    "\n",
    "A location in the landscape is represented by a NumPy array of N 0s and 1s.\n",
    "\n",
    "The `index` attribute of `NKLandscape` is an array of indices into a location, which is an efficient way to select the overlapping slices.\n",
    "\n",
    "The `cache` attribute is a dictionary that maps from `(i, slice)` to a fitness, where `i` indicates which of the `N` functions we want to evaluate and `slice` is the parameters of the function.\n",
    "\n",
    "The first time we see a particular `(i, slice)` pair, we generate a random fitness value.  The we store it in the `cache` in case we need it again."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class NKLandscape:\n",
    "    def __init__(self, N, K, A=2):\n",
    "        \"\"\"Create a landscape.\n",
    "        \n",
    "        N: number of attributes\n",
    "        K: number of interactions\n",
    "        \n",
    "        index is a NumPy array that specifies the interactions between\n",
    "        attributes. (The K+1 entries in each row identify the attributes\n",
    "        that contribute to the fitness of the row attribute.)\n",
    "        \"\"\"\n",
    "        self.N = N\n",
    "        self.K = K\n",
    "        self.A = A\n",
    "        self.cache = dict()\n",
    "        \n",
    "        # compute the powers of 2 up to K, used in lookup()\n",
    "        self.powers = 2 ** np.arange(K, -1, -1)\n",
    "\n",
    "        # compute the index, used in fitness()\n",
    "        iseq = np.array(range(N))\n",
    "        jseq = np.array(range(K+1))\n",
    "        self.index = (iseq[:, None] + jseq[None, :]) % N\n",
    "\n",
    "    def random_loc(self):\n",
    "        \"\"\"Choose a random location.\"\"\"\n",
    "        # in NumPy version 1.11 we can pass dtype to randint\n",
    "        return randint(self.A, size=self.N).astype(np.uint8)\n",
    "    \n",
    "    def lookup(self, i, row):\n",
    "        \"\"\"Look up `row` in function `i`.\n",
    "        \n",
    "        i: int from 0 to N-1\n",
    "        row: array of K+1 0s and 1s\n",
    "        \n",
    "        returns: f_i(row)\n",
    "        \"\"\"  \n",
    "        #key = i, np.sum(self.powers * row)\n",
    "        key = i, tuple(row)\n",
    "        return self.cache.setdefault(key, rand())\n",
    "    \n",
    "    def fitness(self, loc):\n",
    "        \"\"\"Evaluates the fitness of a location.\n",
    "        \n",
    "        loc: array of N 0s and 1s\n",
    "        \n",
    "        returns: float fitness\n",
    "        \"\"\"\n",
    "        slices = loc[self.index]\n",
    "        fs = [self.lookup(i, row) for i, row in enumerate(slices)]\n",
    "        return np.mean(fs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's a small example.  The index shows how the traits are linked.  Trait 0 is linked to traits 1 and 2.  Trait 1 is linked to traits 2 and 3, etc."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 1, 2],\n",
       "       [1, 2, 3],\n",
       "       [2, 3, 4],\n",
       "       [3, 4, 0],\n",
       "       [4, 0, 1]])"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nkland = NKLandscape(5, 2)\n",
    "nkland.index"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's an example that evaluates the fitness at a random location:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([0, 0, 0, 0, 1], dtype=uint8), 0.519)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "loc = nkland.random_loc()\n",
    "loc, nkland.fitness(loc)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's what the landscape cache looks like after one evaluation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{(0, (0, 0, 0)): 0.824,\n",
       " (1, (0, 0, 0)): 0.380,\n",
       " (2, (0, 0, 1)): 0.607,\n",
       " (3, (0, 1, 0)): 0.706,\n",
       " (4, (1, 0, 0)): 0.076}"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nkland.cache"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we evaluate the same location again, we should get the same value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([0, 0, 0, 0, 1], dtype=uint8), 0.519)"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "loc, nkland.fitness(loc)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And if we evaluate a different location, we should get a different value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([1, 0, 1, 0, 0], dtype=uint8), 0.421)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "loc = nkland.random_loc()\n",
    "loc, nkland.fitness(loc)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The agents\n",
    "\n",
    "Here's a parent class, `NKAgent`, that contains code used by all agents:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "class NKAgent:\n",
    "    \"\"\"Represents an agent in an NK model.\"\"\"\n",
    "    \n",
    "    def __init__(self, landscape):\n",
    "        \"\"\"Create an agent at the given location.\n",
    "        \n",
    "        loc: array of N 0s and 1s\n",
    "        landscape: reference to an NKLandscape\n",
    "        \"\"\"\n",
    "        self.landscape = landscape\n",
    "        self.loc = landscape.random_loc()\n",
    "        self.fitness = landscape.fitness(self.loc)\n",
    "        self.moves = 0\n",
    "        self.done = False\n",
    "        \n",
    "    def mutation(self, direction):\n",
    "        \"\"\"Computes the location in the given direction.\n",
    "        \n",
    "        Result differs from the current location along the given axis.\n",
    "        \n",
    "        direction: int index from 0 to N-1\n",
    "        \n",
    "        returns: new array of N 0s and 1s\n",
    "        \"\"\"\n",
    "        new_loc = self.loc.copy()\n",
    "        new_loc[direction] ^= 1\n",
    "        return new_loc\n",
    "    \n",
    "    def generate_mutations(self):\n",
    "        \"\"\"Generates all possible mutations from current location, in random order.\n",
    "        \n",
    "        yields: new array of N 0s and 1s\n",
    "        \"\"\"\n",
    "        for direction in permutation(self.landscape.N):\n",
    "            new_loc = self.mutation(direction)\n",
    "            yield new_loc\n",
    "\n",
    "    def consider(self, new_loc):\n",
    "        \"\"\"Moves if the fitness at `new_loc` is greater than or equal to current fitness.\n",
    "        \n",
    "        new_loc: array of N 0s and 1s\n",
    "        \n",
    "        return: True if the agent moved\n",
    "        \"\"\"\n",
    "        new_fitness = self.landscape.fitness(new_loc)\n",
    "        if new_fitness >= self.fitness:\n",
    "            self.loc = new_loc\n",
    "            self.fitness = new_fitness\n",
    "            self.moves += 1\n",
    "            return True\n",
    "        return False\n",
    "    \n",
    "    def step(self):\n",
    "        \"\"\"Consider at least one mutation.\n",
    "        \n",
    "        Child classes should override this method.\n",
    "        \n",
    "        \"\"\"\n",
    "        pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's an example using the NKAgent parent class."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([0, 1, 0, 0, 1], dtype=uint8), 0.444)"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent = NKAgent(nkland)\n",
    "agent.loc, agent.fitness"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can choose a random direction."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 0, 0, 0, 1], dtype=uint8)"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "direction = randint(nkland.N)\n",
    "new_loc = agent.mutation(direction)\n",
    "new_loc"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And consider moving."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent.consider(new_loc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([0, 0, 0, 0, 1], dtype=uint8), 0.519)"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent.loc, agent.fitness"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following loop considers every direction, in random order, and accepts the first acceptable move."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0 0 0 0 0] 0.552182884067\n",
      "[1 0 0 0 0] 0.589309815618\n"
     ]
    }
   ],
   "source": [
    "for direction in permutation(nkland.N):\n",
    "    new_loc = agent.mutation(direction)\n",
    "    if agent.consider(new_loc):\n",
    "        print(agent.loc, agent.fitness)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can encapsulate that strategy, called the \"fitter\" strategy, in a class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "class NKAgentFitter(NKAgent):\n",
    "    def step(self):\n",
    "        \"\"\"Consider all mutations in order and choose the first improvement.\"\"\"\n",
    "        if self.done:\n",
    "            return False\n",
    "        \n",
    "        for new_loc in self.generate_mutations():\n",
    "            if self.consider(new_loc):\n",
    "                return True\n",
    "            \n",
    "        self.done = True\n",
    "        return False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([0, 1, 1, 0, 0], dtype=uint8), 0.458)"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent = NKAgentFitter(nkland)\n",
    "agent.loc, agent.fitness"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent.step()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([0, 1, 1, 0, 1], dtype=uint8), 0.494)"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent.loc, agent.fitness"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Implement the other strategies described by Vidgen and Padget in [Sendero](http://jasss.soc.surrey.ac.uk/12/4/8.html).\n",
    "\n",
    "Write a class definition called `NKAgentMutant` that implements the one-mutant neighbor strategy and a class definition called `NKAgentGreedy` that implements the greedy strategy.  Instantiate one of each and invoke their step methods."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The simulator\n",
    "\n",
    "Here's a class that runs simulations:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "class NKSimulation:\n",
    "    \n",
    "    def __init__(self, landscape, num_agents, agent_maker):\n",
    "        \"\"\"Create the simulation:\n",
    "        \n",
    "        landscape: NKLandscape\n",
    "        num_agents: int number of agents\n",
    "        agent_maker: function that makes agents\n",
    "        \"\"\"\n",
    "        self.landscape = landscape\n",
    "        self.agents = [agent_maker(landscape) for _ in range(num_agents)]\n",
    "        \n",
    "    def step(self):\n",
    "        \"\"\"Run step on each agent.\n",
    "        \n",
    "        returns: list of boolean, whether each agent moves\n",
    "        \"\"\"\n",
    "        return [agent.step() for agent in self.agents]\n",
    "        \n",
    "    def get_fitnesses(self):\n",
    "        \"\"\"Returns a list of agent fitnesses.\"\"\"\n",
    "        return [agent.fitness for agent in self.agents]\n",
    "    \n",
    "    def get_locations(self):\n",
    "        \"\"\"Returns a list of agent locations.\"\"\"\n",
    "        return [agent.loc for agent in self.agents]\n",
    "    \n",
    "    def get_peaks(self):\n",
    "        \"\"\"Returns the set of unique locations.\n",
    "        \n",
    "        If all agents have run until they reach a local peak,\n",
    "        the result is a subset of the peaks.\n",
    "        \"\"\"\n",
    "        locs = [tuple(loc) for loc in self.get_locations()]\n",
    "        return set(locs)\n",
    "\n",
    "    def get_peak_heights(self):\n",
    "        \"\"\"Returns the set of unique heights.\n",
    "        \n",
    "        If all agents have run until they reach a local peak,\n",
    "        the result is heights of a subset of the peaks.\n",
    "        \"\"\"\n",
    "        return set(self.get_fitnesses())\n",
    "    \n",
    "    def get_path_lengths(self):\n",
    "        \"\"\"Returns the number of moves for each agent.\n",
    "        \n",
    "        If all agents have run until they reach a local peak,\n",
    "        the result is the sequence of path lengths.\n",
    "        \"\"\"\n",
    "        return [agent.moves for agent in self.agents]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And here's an example with small values of `N` and `K`.  Initially the distribution of fitness is centered around 0.5."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.474"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x114444710>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "nkl = NKLandscape(5, 2)\n",
    "nksim = NKSimulation(nkl, 100, NKAgentFitter)\n",
    "cdf_fitness = Cdf(nksim.get_fitnesses())\n",
    "thinkplot.Cdf(cdf_fitness)\n",
    "\n",
    "cdf_fitness.Mean()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's how the distribution of fitness evolves after each step."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\nmU3QDWc9AAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x116b1b0b8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "thinkplot.PrePlot(7)\n",
    "\n",
    "nksim = NKSimulation(nkl, 100, NKAgentFitter)\n",
    "\n",
    "for i in range(7):\n",
    "    cdf_fitness = Cdf(nksim.get_fitnesses())\n",
    "    thinkplot.Cdf(cdf_fitness)\n",
    "    nksim.step()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "After every agent has found a peak, we can plot the distribution of fitness."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'xscale': 'linear', 'yscale': 'linear'}"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x104935128>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "cdf_fitness = Cdf(nksim.get_fitnesses())\n",
    "thinkplot.Cdf(cdf_fitness)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The number of unique locations is a lower bound on the number of peaks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "peaks = nksim.get_peaks()\n",
    "len(peaks)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we can look at the heights of the peaks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{0.602, 0.826}"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "heights = nksim.get_peak_heights()\n",
    "heights"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's the distribution of path lengths.  A few agents are born on a peak.  The longest path is probably 5 or fewer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'xscale': 'linear', 'yscale': 'linear'}"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\nAAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x116b3eda0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "cdf_moves = Cdf(nksim.get_path_lengths())\n",
    "thinkplot.Cdf(cdf_moves)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This function encapsulates the steps for running a simulation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_simulation(N=5, K=2, num_agents=100, agent_maker=NKAgentFitter):\n",
    "    \"\"\"Create a simulation and run until all agents find a peak.\n",
    "    \n",
    "    N: int number of traits\n",
    "    K: int number of interactions\n",
    "    num_agents: int number of agents\n",
    "    agent_maker: function that creates agents\n",
    "    \n",
    "    returns NKSimulation object\n",
    "    \"\"\"\n",
    "    nkl = NKLandscape(N, K)\n",
    "    nksim = NKSimulation(nkl, num_agents, agent_maker)\n",
    "    for i in range(100):\n",
    "        steps = nksim.step()\n",
    "        if np.sum(steps) == 0:\n",
    "            break\n",
    "    return nksim"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's the same small example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "nksim = run_simulation(N=5, K=2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This function takes a completed simulation and summarizes the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_results(nksim):\n",
    "    \"\"\"Summarizes simulation results.\n",
    "    \n",
    "    nksim: NKSimulation object\n",
    "    \"\"\"\n",
    "    cdf_fitness = Cdf(nksim.get_fitnesses())\n",
    "    peaks = nksim.get_peaks()\n",
    "    heights = nksim.get_peak_heights()\n",
    "    cdf_moves = Cdf(nksim.get_path_lengths())\n",
    "    \n",
    "    print(\"Mean fitness: \", cdf_fitness.Mean())\n",
    "    print(\"Mean path length: \", cdf_moves.Mean())\n",
    "    print(\"Number of peaks:\", len(peaks))\n",
    "\n",
    "    thinkplot.PrePlot(cols=2)\n",
    "    thinkplot.Cdf(cdf_fitness)\n",
    "    thinkplot.Config(xlabel='Fitness', ylabel='CDF')\n",
    "\n",
    "    thinkplot.SubPlot(2)\n",
    "    thinkplot.Cdf(cdf_moves)\n",
    "    thinkplot.Config(xlabel='Path Length', ylabel='CDF')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And here are the results with small values of `N` and `K`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Mean fitness:  0.819691789373\n",
      "Mean path length:  1.74\n",
      "Number of peaks: 3\n"
     ]
    },
    {
     "data": {
      "image/png": "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\nbbzJ6hvzahvvaqLXL0/+Jhefdbr4rNPFZX32o4uwnmRLkrNba+e01i5NckqS65M8eQ/z/2aSz7fW\nntNa+2xr7dVJ3jZ+HQCgP3o9AExg8LBeVQcm2ZTRlvMkSWutJXlPkuP2sNiPjZ+f7by9zA8ADESv\nB4DJDR7WkxyRZE2Sq+dMvzrJUXtY5qg9zH94VR28uOUBAAuk1wPAhNYOXcASWpckl1xySb5z3Y58\n62tX3urJiy++KIcdum6QwqZl27ZtufDCC4cuY8mstvEmq2/Mq228yeob82oa7yWXXDLz5cpqPsNa\nlyTfvuqKXHzRRdmwbjV9zJmO1fQ3uVSs08VnnS4u63NxLaTf1+gotOGMD427PsmJrbVzZ01/Y5KN\nrbVH72aZDyS5oLX27FnTnpTkzNbaD+zh5zwuyV8tbvUAsGCPb629ZegipkmvB4DJ+/3gm5xbazdX\n1QVJjk9ybpJUVY2/f9UeFjs/ycPnTPvZ8fQ9OS/J45NckWTHAkoGgMWwLskdM+pPK5peD8AqNu9+\nP/ie9SSpql9N8saMrgz7sYyu9PqYJPdorV1TVS9NcrvW2hPH898xyb8kOSvJX2TU7P8kyc+31uZe\njAYAGJheDwCTGXzPepK01t46vs/q6UmOTHJxkhNaa9eMZzkqydGz5r+iqh6R5Mwkv53kS0meonkD\nQJ/0egCYTBd71gEAAIDv6eHWbQAAAMAswjoAAAB0ZtmG9ao6taour6obquqjVfWAvcz7hqraVVU7\nx//OPP5l1jzvn/PczOPtSzOivVvs8Y7ne1ZVXVpV11fVlVV1RlUdPP3R7J8p/I7XVtUfVNW/jV/z\noqo6YWlGs2+TjHc8/+Or6uKquq6qvlJVr6+qH5wzz69U1SXj1/xkVc29svKgFnvMVXWvqnrb+DV3\nVdVvT38U+28K431qVX2wqr45fvzjvl5zKU1hvI+uqo9X1beqavv4b/gJ0x/J/pvG3/GseR87/n/9\nv6ZT/fI26bpn76rqwVV1blV9efz/7lFD17ScVdXzqupjVXVtVV1dVf+7qu4+dF3LVVWdMv5cs238\n+EhV/dzQda0UVfXc8d/9GUPXslxV1Wn1/bnyMxO/UGtt2T2SnJTRLVl+Pck9kpyd5JtJjtjD/Icl\n+eFZj9sl+XqSF8ya5z/MmedeSW5O8msrdLyPS3LD+LVvn+RnMrp4z8uHHu8Ux/yyJF9MckJGt084\n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\ngg==\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x1144e5eb8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_results(nksim)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's a simulation run with larger `N` and `K`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 3.48 s, sys: 229 ms, total: 3.71 s\n",
      "Wall time: 4.34 s\n",
      "Mean fitness:  0.650388546404\n",
      "Mean path length:  6.04\n",
      "Number of peaks: 100\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n9/VkkJVSjpRSPlxKOVtKeaaU8v+UUr6s7rr6pZTylqXfhXfUXUsvlVKuLaX8QinlM0tr2kdLKdfX\nXVcvlFL2lVL+0dK6Pl9K+S+llP+r7rr2Uinla0op7y+l/NHSv99vXueYo0vv2eaX1rgvraPWvbDZ\nfEsp+0spP1FK+U+llLmlY95TSvmiOmveja38/a449meXjvnu7T7PQIb3UsqhJG9P8tYkL0vy0SQP\nl24jnI38SpKvTfL6JF+W5HCS31nxmDcn+aUk9yb5qiT/KslDpZSv6MUctqMX803y19Kd7yuSfHWS\nP0jy6035penRnJcf+wVJ/nGSR/e06F3o0b/py5J8IMnzk/ztpWO+K8kf9WAK29ajOb8myduWHvPL\nk7whybcm+dEeTGFbdjDf7063gdcXLf3vFyf5syTvW/GYw/S6dcn5Zvhet7Yy5+XHfkEa9rrVJDt8\nPRlkX5PknyT5n5P8jSSXpfu7cKDWqvpg6UOZv5fu3/HQKqX8hSS/kWQxyauSvDjJ/57kv9ZZVw+9\nJcn/muRN6a7f35fk+0op/6DWqvbWVLqNOt+UZE3TsVLK9yf5B+n++/4rSc6l+zo23s8i99Bm851M\n933LD6f7mv0tSV6U7vuYQbXp3++yUsq3pPvavbP341VVDdyfJB9K8tMrfi7pdqH9vg2O/5vpviH6\nC5s85r9I8v5VYyeTvGsY57vOffYleS7Jd9Q9317OeWmej6Ub/n4+yb+se669mm+6X8d0JslY3fPr\n45z/SZJ/t2rsp5I8OmjzXef+tyW5kOR5K8aG5nVrK/Nd55iBft3a6pyb+rrVpD+7/W8/6H+SXJmk\nk+SWumvp8Tyn0/3A9q8n+fdJ3lF3TT2c648nOVF3HX2c768muXfV2INJ3lt3bT2abyfJN68a++Mk\nd634eSbJZ5N8a9319mK+6xxzY5J2ki+uu95ezTfJ/5Dk6XQ/jPtEku/e7mMP3Jn3pbOJNyT54PJY\n1f2v8YEkN21wt29K8kSS7y+l/GEp5XdKKf+4lNJaccxNS4+x0sObPGZf9HC+q02l+8n9n+1N5TvX\n4zm/NckzVVX9fA9K35EezvebshTkSimfLqX856VLLWv/ve/hnB9PcsPy5bKllBcm+YYk/7oH09iy\nHc53tTck+UBVVX+wYmyYXrdWW2++qw3669ZqG825ca9bTbJH/+0H3V9I90xP7b8LPfbOJL9aVdUj\ndRfSB9+U5IlSyvuWtkY8VUq5s+6ieujxJK8spVyXJKWUlyb5q0n+Ta1V9Ukp5UvSvQJr5evY2ST/\nMaP3OvbndRfSC6WUkuS9SX6yqqqP7fRxGvE979t0ZZKxJM+sGn8m3cst1vPCdC8xW0j3zMaVSX4m\nyRVJvnPpmGs2eMxrdl/yrvRqvqv9RLqXbzTh+3N7MudSyi3pnrl66d6XvCu9+jt+YbpnJx5I8uok\nX7p0zP4k/2jvyt+Rnsy5qqrjS5fJPrb0IjmW5GerqvqJPZ/B9uxkvp+3dFn4q5N826qbhul16/M2\nme9qg/669XkbzbnBr1tNsqv/9oNu6bXuWJLHqqr67brr6ZVSyrele5ntjXXX0icvTPL3090O8qPp\nXkZ9TyllsaqqX6i1st748XTPNH+8lNJO94qjH6yq6l/UW1bfXJNucG3imt5zpdub6MeT/FJVVXN1\n19Mjb0lyvqqqf7qbBxnE8L4T+9K9fOE1y/8gSinfm+RXSilvqqpqsdbq9t625ltKeUu6+4L/WlVV\n5/te7d7YdM7pnp17b5LvqqpqGPaLbeXveF+6L/p/b+ks1G+WUr44yf+R+sP7TlxyzqWUVyT5gXS3\nDHw43Q8s7imlfKqqqh+pqe69cEe6+xwHeS/YdtyRS8x3SF63Vrojq+ZcSpnOcL1u0RvvSvIV6Z6l\nHEpLa9exJH+jqqrP1V1Pn+xL8uGqqn5o6eePllL+crrr2zCG90NJXpPuB5i/ne4HNT9dSvnjIf2w\ngiWllP3p9jSq0t0vPnRKKTek2+fmZbt9rEEM759Jdz/E1avGr07y6Q3u86kkf7Tqk5yPpbsn7ouT\n/O7SfbfzmP3Sq/kmSUop/0e6TUFeWVXVb+1V0bvUizlPJ/lLSX516SxFstSwsZRyPsmLqqr6xN6U\nv229+jv+VLqf8FWrjrmmlLK/qqoLe1H8DvVqzkeT/MKKy4t/aykAvTtJneF9J/Nd6fXp7vtb/Xc2\nTK9bK2003yRD9bq10npz/h/T3NetJtntf/uBVUr5p+luDfqaqqo+VXc9PXRDkquSPLXid2Esya1L\nDc0mVq11w+BT6a5xK30s3Qa0w+gnk7ytqqpfWfr5t5YadR7JcH5Ysdqn030/c3UuPvt+dZLfrKWi\nPlgR3J+X5K8P8Vn3W9J9DfuDL7yEZSzJO0op31NV1Qu3+kC1733drqVPXJ9M8srlsaUX8lemu19m\nPb+R5NpSyuSKsRelexbvD5d+PrnyMZd83dJ4bXo435RSvi/JDyZ5VVVVjXlh6NGcP57kf0r3k9yX\nLv15f5JHlv7/Zvtqe6qHf8e/ke6Z56w65lM1B/deznky3YZfK3VWPH4tdjjf5eNekW6I+7l1bh6m\n163l416Rjec7bK9by8e9IuvP+WNp6OtWk+zmv/0gWwrufyvJ11ZV9XTd9fTYB7L2d+GJdLeFvXQI\ng3vSXfNWb/t4UZLfr6GWfphM90O4lToZwKyyE0sfxH46F7+OzaTblXwoX8dWBPcXpvth/DBfYfbe\nJF+ZL7x+vTTdBoU/me63SWzdXnbW69efdC+VnE/yd9P9Ool3J3k2yVVLt78tyXtWHD+V7ovdL6fb\n3e/WdLuV/uyKY25K9+s4vjfdF8d/mO7e2q8Y0vl+/9L8viXdT/WW/0zVPd9ezXmd52hM1+Ye/R1/\ncbpNP+5Jcl2Sb0x3YXhL3fPt4ZzfujTnQ0lekG6QPZPuHqqBmu+K+/1Cksc3eMyhed3a4nyH6nVr\nK3Ne59jGvG79/+3deYykRR2H8eeLsCEKmJgIqEGCHDEgh3HFiAZCIMrhYpQgt6AIKAG5FESuRVCB\nIAkS5BJWjUJAiRyChOhiQIICAV1AOSLHcqyyCro77HLIln9UNbTNsjszO0P3zDyf5M10d9X7dlXP\nzFvvr96q6kHalvfZT7aNOlT+Oeo6IN3/C6v2u2xv4mcw2Vebn97O78dRO/f2AhYCe/S7bONU31nU\nVbh3oo44+gzwDPCdfpdtDOv4NmrQtgW1Y+KI9nydln5MO2/NoHZWXd2uYab1u+xjXV/q6O9rqNd1\nm/acx1bpd9nH4/e7lPyjWm2+7xVdgQ/oEOAx6lco3A5M70qbBczuyb8RdRXmofaHciZ1mFV3nl2p\nd2gXA3Ood3b6XtfxqG/7g3llKdtJ/a7reP6Oe/IP1EXwOP1Nd3psF7UG4Fgg/a7rOP5drwScCDxE\n/X7Ux6idF2v0u66jrO8ara5fXMYxJ9N5a5n1naTnreX+jnvyD9R5a5C2ZX32k22jXhgu7X/h8/0u\n25v4GcxmEgfvrY47tfP6IuD+4Z4nJuJGDXzObuf559s1yynAyv0u2xjWcZs3+N+9tCvPTOod2UXt\nemeDfpd7POpL7aDpTes837rfZR+v329P/kcYRfCetrMkSZIkSRpQU2IeiSRJkiRJE5nBuyRJkiRJ\n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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x117439390>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%time nksim = run_simulation(N=40, K=20)\n",
    "plot_results(nksim)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Starting with `N=5` and `K=3`, run simulations with increasing values of `N`, keeping `K` constant, and plot the number of peaks and mean path length as a function of `N`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Starting with `N=20` and `K=0`, run simulations with increasing values of `K`, keeping `N` constant, and plot the number of peaks and mean path length as a function of `K`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Neutral networks\n",
    "\n",
    "Consider the extension of the NK model proposed by Newman and Engelhardt (1998), in which fitness values are quantized rather than continuous. Below is a subclass of NKLandscape that implements this extension, which is sometimes called the NKq model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "class NKqLandscape(NKLandscape):\n",
    "    def __init__(self, N, K, A=2, F=2):\n",
    "        \"\"\"Create a landscape with quantized fitness values.\n",
    "        \n",
    "        N: number of attributes\n",
    "        K: number of interactions\n",
    "        A: number of alleles per attribute\n",
    "        F: number of quantized fitness values\n",
    "        \n",
    "        index is a NumPy array that specifies the interactions between\n",
    "        attributes. (The K+1 entries in each row identify the attributes\n",
    "        that contribute to the fitness of the row attribute.)\n",
    "        \"\"\"\n",
    "        NKLandscape.__init__(self, N, K, A)\n",
    "        self.F = F\n",
    "    \n",
    "    def lookup(self, i, row):\n",
    "        \"\"\"Look up `row` in function `i`.\n",
    "        \n",
    "        i: int from 0 to N-1\n",
    "        row: array of K+1 0s and 1s\n",
    "        \n",
    "        returns: f_i(row)\n",
    "        \"\"\"\n",
    "        key = i, tuple(row)\n",
    "        return self.cache.setdefault(key, randint(self.F)/(self.F-1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "nkqland = NKqLandscape(5, 2, 2, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([1, 0, 1, 1, 0], dtype=uint8), 0.800)"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "loc = nkqland.random_loc()\n",
    "loc, nkqland.fitness(loc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{(0, (1, 0, 1)): 1.000,\n",
       " (1, (0, 1, 1)): 1.000,\n",
       " (2, (1, 1, 0)): 1.000,\n",
       " (3, (1, 0, 1)): 0.000,\n",
       " (4, (0, 1, 0)): 1.000}"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nkqland.cache"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The NKq model gives rise to \"neutral networks\". A neutral network is \"a set of sequences [locations] that all possess the same fitness and that are connected together via ... point mutations\" (p. 1335). Newman and Engelhardt (hereafter NE) discover some striking properties of these networks, which, they suggest, greatly improve the performance of single-mutation evolutionary search on rugged fitness landscapes. In the exercises below, we ask you to replicate and interpret some of their results."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "### Detecting and analyzing neutral networks\n",
    "\n",
    "To get you started, here's a class that might help.  It keeps track of a collection of nodes that are at the same fitness level.  It figures out which nodes are one-bit neighbors and creates edges between them.  Then it uses NetworkX to find all connected components.\n",
    "\n",
    "The tricky part of this implementation is that it converts each location from an array of 0s and 1s to an integer; it uses these integers as nodes in the graph, and it uses bitwise operations on these integers to check for neighbor relationships."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "import networkx as nx\n",
    "\n",
    "class GraphOfLoc:\n",
    "    \"\"\"Represents a graph of locations.\"\"\"\n",
    "    \n",
    "    def __init__(self, N):\n",
    "        self.g = nx.Graph()\n",
    "        \n",
    "        # precompute the powers of 2, used in add()\n",
    "        self.powers = 2 ** np.arange(N-1, -1, -1)\n",
    "        \n",
    "    def add(self, new_loc):\n",
    "        \"\"\"Adds a location to a graph.\n",
    "        \n",
    "        new_loc: location in the form of an int\n",
    "        \"\"\"\n",
    "        locint = np.sum(self.powers * new_loc)\n",
    "        self.g.add_node(locint)\n",
    "\n",
    "        if len(self.g):\n",
    "            # put the other locs into an array\n",
    "            other_locs = np.array(self.g.nodes())\n",
    "            \n",
    "            # compute the indices of the neighbors\n",
    "            index = np.in1d(locint ^ other_locs, self.powers)\n",
    "            neighbors = other_locs[index]\n",
    "            \n",
    "            # add edges from the new loc to all neighbors\n",
    "            self.g.add_edges_from([(locint, n) for n in neighbors])\n",
    "        \n",
    "    def components(self):\n",
    "        \"\"\"Connected components of the graph.\n",
    "        \n",
    "        returns: iterator of sets of nodes.\n",
    "        \"\"\"\n",
    "        return nx.algorithms.connected_components(self.g)\n",
    "    \n",
    "    def component_sizes(self):\n",
    "        return [len(component) for component in self.components()]\n",
    "        \n",
    "    def __str__(self):\n",
    "        return str(self.g)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's a small example that shows how it works."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "([0], [])"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph = GraphOfLoc(5)\n",
    "graph.add([0,0,0,0,0])\n",
    "graph.g.nodes(), graph.g.edges()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we add another node that is a neighbor of the first, we get an edge between them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "([0, 1], [(0, 1)])"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.add([0,0,0,0,1])\n",
    "graph.g.nodes(), graph.g.edges()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's another node that is also a neighbor of the first (but not of the second)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "([0, 1, 2], [(0, 1), (0, 2)])"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.add([0,0,0,1,0])\n",
    "graph.g.nodes(), graph.g.edges()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And here's a node that's not connected."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "([0, 1, 2, 24], [(0, 1), (0, 2)])"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.add([1,1,0,0,0])\n",
    "graph.g.nodes(), graph.g.edges()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we print the sizes of the connected components, we get one set of 3 nodes and 1 unconnected node."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3\n",
      "1\n"
     ]
    }
   ],
   "source": [
    "for component in graph.components():\n",
    "    print(len(component))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's find all connected components for all fitness levels in a quantized landscape."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "N = 10\n",
    "K = 4\n",
    "nkqland = NKqLandscape(N, K)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`make_locs` makes an array that contains the binary representation of all locations in a landscape."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_locs(N):\n",
    "    \"\"\"Makes an array of binary numbers from 0..2**N-1.\n",
    "    \n",
    "    returns: array of 1s and 0s with 2**N rows and N cols\n",
    "    \"\"\"\n",
    "    # array of numbers\n",
    "    locints = np.arange(2**N, dtype=np.uint64)\n",
    "    \n",
    "    # array of 1s and 0s\n",
    "    locs = np.zeros((N, len(locints)), dtype=np.uint8)\n",
    "    \n",
    "    # fill in the rows\n",
    "    for i in range(N):\n",
    "        locs[i] = locints % 2\n",
    "        locints >>= 1\n",
    "    \n",
    "    # flip and transpose\n",
    "    return np.flipud(locs).transpose()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For `N=10`, there are `2**N = 1024` locations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1024"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "locs = make_locs(N)\n",
    "len(locs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`collect_graph` enumerates the locations in the landscape, computes the fitness of each, and makes a dictionary that maps from each fitness level to a `GraphOfLoc` that contains the locations at that fitness level."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import defaultdict\n",
    "\n",
    "def collect_graphs(nkqland):\n",
    "    locs = make_locs(nkqland.N)\n",
    "    d = defaultdict(lambda: GraphOfLoc(nkqland.N))\n",
    "\n",
    "    for loc in locs:\n",
    "        fitness = nkqland.fitness(loc)\n",
    "        d[fitness].add(loc)\n",
    "        \n",
    "    return d"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 148 ms, sys: 2.71 ms, total: 150 ms\n",
      "Wall time: 150 ms\n"
     ]
    }
   ],
   "source": [
    "%time d = collect_graphs(nkqland)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can summarize the results by printing the fitness levels and the sizes of the connected components at each level."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "def summarize_graphs(d):\n",
    "    for fitness, graph in sorted(d.items()):\n",
    "        sizes = graph.component_sizes()\n",
    "        sizes.sort(reverse=True)\n",
    "        print(fitness, sizes)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.1 [5, 4, 1, 1, 1]\n",
      "0.2 [14, 6, 5, 2, 1, 1, 1, 1, 1]\n",
      "0.3 [99, 13, 4, 1, 1]\n",
      "0.4 [157, 5, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n",
      "0.5 [238, 3, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n",
      "0.6 [205, 5, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n",
      "0.7 [63, 15, 6, 4, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n",
      "0.8 [37, 7, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n",
      "0.9 [3, 2, 2, 1, 1, 1, 1, 1]\n",
      "1.0 [1, 1]\n"
     ]
    }
   ],
   "source": [
    "summarize_graphs(d)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is a function that extracts the sizes of the components. (Recall that in the language of the NE paper, each component in this graph is a neutral network.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "def all_component_sizes(d):\n",
    "    \"\"\"Extract the sizes of the components.\n",
    "    \n",
    "    returns: list of int\n",
    "    \"\"\"\n",
    "    t = []\n",
    "    for fitness, graph in d.items():\n",
    "        t.extend(graph.component_sizes())\n",
    "    return t"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And here is a function that computes the fraction of locations (or sequences, in NE's terminology) that reside in \"common\" networks, where a common network is one that is larger than average."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def prob_common(sizes):\n",
    "    \"\"\"Computes the fraction of sequences in common neutral networks.\n",
    "    \n",
    "    sizes: list of component sizes\n",
    "    \"\"\"\n",
    "    mean = np.mean(sizes)\n",
    "    total = np.sum(sizes)\n",
    "    common = np.sum([size for size in sizes if size>mean])\n",
    "    return common / total"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, here is a function that runs a neutral network experiment. It takes values for `N`, `K`, and `F` (leaving the default value of `A`=2), creates an appropriate NKq landscape, instantiates a bunch of agents (by default, 100 agents that use the \"fitter\" strategy), and lets them walk the landscape.\n",
    "\n",
    "The `run_experiment` function returns three things: a Cdf of neutral network sizes, the fraction of locations that reside in common networks, and the maximum fitness achieved by the agents. You may find these things remarkably useful in the exercises below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def run_experiment(N, K, F=2, num_agents=100, agent_maker=NKAgentFitter):\n",
    "    \"\"\"Runs an experiment with the given parameters and return summaries.\n",
    "    \n",
    "    N: number of attributes\n",
    "    K: number of interactions\n",
    "    F: number of weights\n",
    "    num_agents: int number of agents\n",
    "    agent_maker: function that makes agents\n",
    "    \n",
    "    returns: Cdf of component (neutral network) sizes,\n",
    "             float fraction of locations in a common network,\n",
    "             float maximum fitness acheived by the agents\n",
    "    \"\"\"\n",
    "    nkqland = NKqLandscape(N, K, F=F)\n",
    "    \n",
    "    d = collect_graphs(nkqland)\n",
    "    sizes = all_component_sizes(d)\n",
    "    cdf = Cdf(sizes)\n",
    "    pc = prob_common(sizes)\n",
    "    \n",
    "    nksim = NKSimulation(nkqland, num_agents, agent_maker)\n",
    "    for i in range(100):\n",
    "        steps = nksim.step()\n",
    "        if np.sum(steps) == 0:\n",
    "            break\n",
    "\n",
    "    max_fit = np.max(nksim.get_fitnesses())\n",
    "    \n",
    "    return cdf, pc, max_fit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Your turn!\n",
    "\n",
    "** Exercise:** Run experiments for a single value of `N` (e.g., 14) and several values of `K` (e.g., 1, 2 and 5). Plot the distribution of neutral network sizes. Compare your results with Figure 1 in the NE paper."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "** Exercise:** Run experiments for a range of `N` values (e.g., 8 to 16) and several values of `K` (e.g., 2, 4 and 6). For each value of `K`, plot the fraction of locations that reside in common neutral networks. Compare your results with Figure 2 in the NE paper."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "** Exercise:** Run experiments for a single pair of `N` and `K` values (e.g., 14 and 4) and a range of `F` values (e.g., 2 to 16). Plot the maximum fitness achieved as a function of `F`. Compare your results with Figure 3 in the NE paper."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "**Exercise:** Putting these findings together, explain in your own words how neutrality helps an evolving population reach a higher level of fitness. How might you be able to harness this phenomenon in designing engineered artifacts and systems?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
